# ===== directories settings =====
# path settings
# the folder of object files (*.o)
OBJ_DIR := ./obj
# the folder of lib files (*.so)
LIB_DIR := ./lib
# the folder of header files (*.h)
HEADER_DIR := ./include
# the folder of source files (*.c)
SRC_DIR := ./src
# the folder of external commands
EXTERN_DIR := ./extern


# files list
HEADERS := $(wildcard $(HEADER_DIR)/*.h)
SRCS := $(wildcard $(SRC_DIR)/*.c)
COUNT_SRCS := $(shell find $(SRC_DIR) -name "*.c" | wc -l)
OBJS := $(patsubst $(SRC_DIR)/%.c,$(OBJ_DIR)/%.o,$(SRCS))
OBJS4SHAREDLIBS := $(filter-out $(OBJ_DIR)/minishell.o,$(OBJS))
EXTERN_SRCS := $(wildcard $(EXTERN_DIR)/src/*.c)
EXTERN_EXE := $(patsubst $(EXTERN_DIR)/src/%.c, $(EXTERN_DIR)/bin/%, $(EXTERN_SRCS))

MAIN := ./minishell

ifneq ($(COUNT_SRCS),0)
LIB_NAMES := minishell
SHARED_LIBS := $(addprefix $(LIB_DIR)/lib, $(addsuffix .so, $(LIB_NAMES)))
LDLIBS := $(addprefix -l, $(LIB_NAMES)) -lm
# the libs required in the stage of linking, where the lib of <math.h> was added by "-lm" \
-lm is a linking option to tell the linker to link the math library. \
why should we link the math library? Because the function declared in math.h was implemented in the math library. \
Linking errors will occur with linker failing to find the implementation of the math functions if you don't specify -lm while comiling the program including math.h.
else
# when there's no srouce files, there are only math libbrary reserved.
EXISTING_LIBS := $(wildcard $(LIB_DIR)/*.so)
ifneq ($(EXISTING_LIBS),)
LDLIBS := $(patsubst $(LIB_DIR)/lib%.so,-l%,$(EXISTING_LIBS)) -lm
SHARED_LIBS :=
endif
endif

# dependent packages
DEPS := gcc valgrind trash-cli make

# ===== compiler settings =====
# specify the compiler as gcc
CC :=gcc

# search for the header files specified by $(IDIR)
CFLAGS := -I$(HEADER_DIR) -Wall -Wextra -Werror

# search for the lib files specified by $(LIBDIR)
LDFLAGS := -L$(LIB_DIR) -Wl,-rpath=$(LIB_DIR)

# ========= define phony targets =========
.PHONY: all minishell run memcheck clean GIT_LOGS dependencies_download compileExternalCMD cleanExternalCMD

# ===== make rules =====
all: | minishell run

minishell: $(OBJ_DIR)/minishell.o $(SHARED_LIBS) 
	$(QUIET)$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(LDLIBS)

run:
	$(MAIN)

memcheck: minishell
	valgrind --tool=memcheck --track-origins=yes --leak-check=full ./minishell

clean: $(OBJ_DIR) $(LIB_DIR)
ifneq ($(wildcard $(OBJ_DIR)/*),)
	trash $(OBJ_DIR)/*
endif

ifneq ($(wildcard $(LIB_DIR)/*),)
	trash $(LIB_DIR)/*
endif

ifneq ($(wildcard $(MAIN)),)
	trash $(MAIN)
endif	

dependencies_download: 
	sudo apt update
	sudo apt install -y $(DEPS)

compileExternalCMD: $(EXTERN_EXE)
	@echo "Compiling external commands has done."

cleanExternalCMD:
	rm -rf $(EXTERN_DIR)/bin/*

# ===== dependent rules =====
# compile the source file of minishell.c into object file
$(OBJ_DIR)/minishell.o: minishell.c $(HEADERS) | $(OBJ_DIR)
	$(QUIET) $(CC) -fPIC -c $(CFLAGS) $< -o $@
	@echo "Compiled minishell.c into minishell.o."

ifneq ($(COUNT_SRCS), 0)
# compile the source file of lib*.c into object file
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c $(HEADERS) | $(OBJ_DIR)
	$(QUIET)$(CC) -fPIC -c $(CFLAGS) $< -o $@
	@echo "Compiled $< into $@."

# compile the source file of *.o into shared library
$(LIB_DIR)/libminishell.so: $(OBJS4SHAREDLIBS) | $(LIB_DIR)
	$(QUIET)$(CC) -shared -o $@ $^

endif

# dependent rules for target compileExternalCMD
$(EXTERN_DIR)/bin/%: $(EXTERN_DIR)/src/%.c 
	$(CC) $< -o $@

# $(info $(shell find $(addprefix $(SRC_DIR),/*) | wc -l))

# Unify directory creation rules
$(OBJ_DIR):
	mkdir -p $@
	@echo "Created directory $@."

$(LIB_DIR):
	mkdir -p $@
	@echo "Created directory $@."

# ===== forbid the implicit rule =====
MAKEFLAGS += --no-builtin-rules
.SUFFIXES:

# ===== control information flow of compilation =====
QUIET = @printf "\033[34mBuilding $@...\033[0m\n"; # hide the details of compilation by default
ifeq ($(V),1) # if V is 1, show the details of compilation, eg: `make V=1`
	QUIET =
endif

# ===== git add, git commit and ammend the logs =====
GIT_LOGS:
	git add .
	git config --global core.editor "vim"
	git commit

# ===== install the target of minishell to the folder of /usr/local/bin/minishell =====
# install: minishell
# 	install -d /usr/local/bin
# 	install -m 755 minishell /usr/local/bin/
# 	$(shell sudo mkdir -p /usr/local/etc/minishell && export $(PATH)+=)
# 	@echo "Installed to /usr/local/bin/minishell"

# uninstall: 
# 	trash /usr/local/bin/minishell
# 	@echo "Uninstalled minishell from /usr/local/bin."
